import javax.swing.*;//Swing是Java的一个扩展GUI工具包，用于创建窗口、按钮、文本框等图形界面元素。星号(*)表示导入此包下的所有类。
import java.awt.*;//是Abstract Window Toolkit（抽象窗口工具包）的缩写，它提供了一套基本的GUI图形界面元素，如窗口、按钮、文本框等。
import java.awt.event.ActionEvent;//是AWT事件系统中用来表示组件上发生动作事件（如按钮点击）的类。动作事件通常与组件的动作相关联，如按钮按下
import java.awt.event.ActionListener;//是一个接口，用于接收动作事件。任何实现了ActionListener接口的类都需要定义actionPerformed方法，该方法在动作事件发生时被调用。
import java.awt.event.MouseAdapter;//是一个适配器类，用于处理鼠标事件。通过继承这个类，可以只覆盖感兴趣的事件处理方法，而不是实现MouseListener接口中的所有方法。
import java.awt.event.MouseEvent;//一个用来表示鼠标事件的类。它可以包含鼠标点击、移动、拖动等信息。
import java.util.ArrayList;//是一个可以动态调整大小的数组实现，是List接口的可调整大小的数组实现。它允许包含重复的元素和null值。
import java.util.List;//是一个接口，表示一个有序集合，可以包含重复的元素。它继承自Collection接口，并添加了添加、获取、删除、替换和查看列表中的元素的方法

// 五子棋游戏主类，继承自JFrame
public class GobangGame extends JFrame {
    // 棋盘的行数和列数
    private static final int ROWS = 15;
    private static final int COLS = 15;
    // 棋盘格子的大小（像素）
    private static final int CELL_SIZE = 30;
    // 存储棋盘状态，0表示空，1表示玩家1的棋子，2表示玩家2的棋子
    private int[][] board = new int[ROWS][COLS];
    // 用于记录落子顺序的列表
    private List<Integer> moveRecord = new ArrayList<>();
    // 当前轮到的玩家，1表示玩家1，2表示玩家2
    private int currentPlayer = 1;

    public GobangGame() {                              // 构造方法，当创建GobangGame类的实例时，这个方法会被调用。在Java中，使用new关键字来实例化一个对象。
        initUI();										// 调用initUI()方法，这个方法（假设在类中已定义）用于初始化用户界面，设置窗口中的组件和布局。
        setTitle("五子棋游戏");						   // 设置窗口的标题为"五子棋游戏"。
        setSize(COLS * CELL_SIZE, ROWS * CELL_SIZE + 80);
// 设置窗口的大小。这里假设COLS和CELL_SIZE是类中定义的常量，分别代表棋盘的列数和每个单元格的大小。窗口的高度是行数乘以单元格大小，再加上80像素（可能是为了显示其他控件，如状态栏）。
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置窗口的默认关闭操作。当用户点击窗口关闭按钮时，程序会退出。
        setLocationRelativeTo(null);					// 设置窗口相对于屏幕的位置。传递null参数会使窗口居中显示。
    }

    //// 初始化游戏界面的方法
    private void initUI() {//是一个访问修饰符，表示initUI方法只能在其所属的类内部被访问,这个方法没有返回值。initUI()：这是一个方法定义，用于初始化用户界面
        JPanel panel = new JPanel() {//声明一个名为panel的局部变量，类型为JPanel,它是Swing库中的一个组件，用于容纳其他组件或者绘制图形。,创建一个JPanel的实例。
            @Override//这是一个注解，表示下面的方法paintComponent是重写了父类（即JPanel）的方法。
            protected void paintComponent(Graphics g) {//protected：这是一个访问修饰符，表示paintComponent方法可以在类内部、同一个包内的其他类以及子类中被访问。void paintComponent(Graphics g)：这是一个方法定义，用于绘制组件。Graphics类型的参数g是一个绘图上下文，用于绘制图形。
                super.paintComponent(g);//调用父类（JPanel）的paintComponent方法。这是必要的，因为它会处理组件的基本绘制，比如背景色等
                drawBoard(g);//调用一个自定义的方法drawBoard，传入Graphics对象g作为参数，用于在面板上绘制棋盘。
                drawPieces(g);//调用一个自定义的方法drawPieces，传入Graphics对象g作为参数，用于在面板上绘制棋子。
            }
        };
        panel.setPreferredSize(new Dimension(COLS * CELL_SIZE, ROWS * CELL_SIZE));//设置了名为panel的组件的首选大小。Dimension对象通过接收两个参数（宽度和高度）来创建。这里，宽度是COLS（列数）乘以CELL_SIZE（每个单元格的尺寸），高度是ROWS（行数）乘以CELL_SIZE。这意味着面板的尺寸是由单元格的数量和每个单元格的大小决定的。
        add(panel);//将panel组件添加到当前容器中。这个容器可能是窗口或者其他类型的容器，panel将在其中显示。

        JButton resetButton = new JButton("重新开始");//创建了一个新的JButton对象，并将其赋值给变量resetButton。按钮上显示的文本是"重新开始
        resetButton.addActionListener(new ActionListener() {//为resetButton按钮添加了一个事件监听器。当按钮被点击时，将触发actionPerformed方法。这里使用了匿名内部类来创建ActionListener的实例。
            @Override//表示下面的方法actionPerformed是重写了ActionListener接口中的actionPerformed方法。
            public void actionPerformed(ActionEvent e) {//开始定义actionPerformed方法的实现。这个方法会在按钮被点击时自动调用。
                resetGame();//是actionPerformed方法中的第一条语句，它调用了resetGame方法。这个方法应该负责重置游戏的状态
                repaint();//是actionPerformed方法中的第二条语句，它调用repaint方法来请求组件重新绘制自己。这通常用于更新界面以反映最新的游戏状态。
            }
        });

        JButton exitButton = new JButton("退出游戏");//创建了一个新的JButton对象，并将其赋值给变量exitButton。按钮上显示的文本是"退出游戏"。
        exitButton.addActionListener(new ActionListener() {//为exitButton按钮添加了一个事件监听器。当按钮被点击时，将触发actionPerformed方法。这里使用了匿名内部类来创建ActionListener的实例。
            @Override//这是一个Java注解，表示下面的方法actionPerformed是重写了ActionListener接口中的actionPerformed方法。
            public void actionPerformed(ActionEvent e) {//开始定义actionPerformed方法的实现。这个方法会在按钮被点击时自动调用。
                System.exit(0);//调用了System类的exit方法，并传递了一个参数0。System.exit(0);用于终止当前运行的Java虚拟机。参数0通常表示正常退出，非零值通常表示异常终止。在这个例子中，点击"退出游戏"按钮将导致程序立即终止运行。
            }
        });

        JPanel buttonPanel = new JPanel();//创建了一个新的JPanel对象，并将其赋值给变量buttonPanel。JPanel是一个轻量级容器，可以用来容纳其他组件，如按钮、文本框等。
        buttonPanel.add(resetButton);//将一个名为resetButton的按钮组件添加到buttonPanel面板中。这里假设resetButton是一个已经创建好的JButton对象。
        buttonPanel.add(exitButton);//将一个名为exitButton的按钮组件添加到buttonPanel面板中。同样，这里假设exitButton是一个已经创建好的JButton对象。
        add(buttonPanel, BorderLayout.SOUTH);//将buttonPanel面板添加到当前容器的南部（底部）。这里使用了BorderLayout布局管理器，它是JPanel的默认布局管理器。BorderLayout将容器划分为五个区域：北、南、东、西、中，SOUTH表示将buttonPanel放置在底部。

        addMouseListenerToPanel(panel);//调用了addMouseListenerToPanel方法，并将一个名为panel的组件作为参数传递给它。这个方法的作用是为panel组件添加一个鼠标监听器。
    }

    //// 给棋盘面板添加鼠标点击监听，用于处理落子操作
    private void addMouseListenerToPanel(JPanel panel) {// 定义一个方法，用于给传入的JPanel对象添加一个鼠标监听器。
        panel.addMouseListener(new MouseAdapter() {// 给panel添加一个匿名内部类的鼠标适配器（MouseAdapter），用于处理鼠标事件。
            @Override
            public void mouseClicked(MouseEvent e) {//重写mouseClicked方法，当鼠标在panel上点击时，这个方法会被调用。
                int col = e.getX() / CELL_SIZE;//计算鼠标点击的列位置。e.getX()获取鼠标点击的x坐标，除以CELL_SIZE（格子大小）得到列索引。
                int row = e.getY() / CELL_SIZE;//计算鼠标点击的行位置。e.getY()获取鼠标点击的y坐标，除以CELL_SIZE得到行索引。
                if (board[row][col] == 0) {//检查在board数组中对应行和列的位置是否为空（即值为0）
                    board[row][col] = currentPlayer;//如果位置为空，则在该位置上标记当前玩家的数字（1或2）。
                    moveRecord.add(row * COLS + col);//将玩家的这一步记录下来，通过计算行索引和列索引的线性组合。
                    if (checkWin(currentPlayer)) {// 调用checkWin方法检查当前玩家是否获胜。
                        String winner = currentPlayer == 1? "玩家1" : "玩家2";//如果当前玩家获胜，根据currentPlayer的值（1或2）设置获胜者信息。
                        JOptionPane.showMessageDialog(null, winner + " 获胜！", "游戏结束", JOptionPane.INFORMATION_MESSAGE);//弹出一个对话框，显示获胜者的信息，并告知游戏结束。
                        resetGame();//调用resetGame方法重置游戏，准备新一轮游戏。
                    } else {// 如果当前玩家没有获胜，则执行以下代码。
                        //// 切换玩家
                        currentPlayer = currentPlayer == 1? 2 : 1;//切换当前玩家，如果当前玩家是1，则切换为2，反之亦然。
                    }
                    repaint();//调用repaint方法重新绘制面板，以显示最新的游戏状态。
                }
            }
        });
    }

    //// 绘制棋盘的方法
    private void drawBoard(Graphics g) {// 定义一个私有方法drawBoard，它接受一个Graphics对象g作为参数。Graphics对象g是用于绘制图形和文本的上下文，通常在组件的paint方法中使用。
        g.setColor(Color.BLACK);// 调用Graphics对象的setColor方法，将绘图颜色设置为黑色。所有的线条和图形都将使用这种颜色绘制。
        for (int i = 0; i < ROWS; i++) {// 开始一个for循环，循环变量i从0开始，直到小于ROWS为止。ROWS是一个类级别或方法可见的变量，代表棋盘的行数。
            g.drawLine(0, i * CELL_SIZE, COLS * CELL_SIZE, i * CELL_SIZE);//用Graphics对象的drawLine方法，绘制一条从点(0, i * CELL_SIZE)到点(COLS * CELL_SIZE, i * CELL_SIZE)的直线。这将绘制棋盘的水平线。每次循环i增加CELL_SIZE，绘制下一行。
        }
        for (int j = 0; j < COLS; j++) {//和上一循环相同，上一循环是关于行，这一循环是关于列
            g.drawLine(j * CELL_SIZE, 0, j * CELL_SIZE, ROWS * CELL_SIZE);
        }
    }

    //// 绘制棋子的方法
    private void drawPieces(Graphics g) {// 定义一个私有方法drawPieces，它接受一个Graphics对象g作为参数。
        for (int row = 0; row < ROWS; row++) {// 开始一个外层循环，遍历棋盘的每一行。ROWS是一个类变量，代表棋盘的行数
            for (int col = 0; col < COLS; col++) {// 在每一行内，开始一个内层循环，遍历棋盘的每一列。COLS是一个类变量，代表棋盘的列数。
                if (board[row][col] == 1) {// 检查当前位置(row, col)的棋盘元素是否等于1。通常1代表黑色棋子。
                    g.setColor(Color.BLACK);// 如果是黑色棋子，设置Graphics对象的颜色为黑色。
                    g.fillOval(col * CELL_SIZE - CELL_SIZE / 2 + 5, row * CELL_SIZE - CELL_SIZE / 2 + 5, CELL_SIZE - 10, CELL_SIZE - 10);
					// 在棋盘上绘制一个黑色的圆形棋子.col * CELL_SIZE - CELL_SIZE / 2 + 5 计算棋子的横坐标位置。
					// CELL_SIZE - 10 是棋子的宽度和高度，减去10是为了留出边距。row * CELL_SIZE - CELL_SIZE / 2 + 5 计算棋子的纵坐标位置。
                } else if (board[row][col] == 2) {// 如果当前位置的棋盘元素不等于1，则检查是否等于2。通常2代表白色棋子。
                    g.setColor(Color.WHITE);// 如果是白色棋子，设置Graphics对象的颜色为白色。
                    g.fillOval(col * CELL_SIZE - CELL_SIZE / 2 + 5, row * CELL_SIZE - CELL_SIZE / 2 + 5, CELL_SIZE - 10, CELL_SIZE - 10);
                    // 在棋盘上绘制一个白色的圆形棋子，计算方式与黑色棋子相同。
                }
            }// 结束内层循环。
        }// 结束外层循环。
    }// 结束方法定义。

    //// 判断指定玩家是否获胜的方法
    private boolean checkWin(int player) {// 定义一个私有方法checkWin，它接受一个整数参数player。player代表玩家的标识（比如1或2).方法返回一个布尔值，表示玩家是否获胜。
        //// 检查横向
        for (int row = 0; row < ROWS; row++) {// 开始一个外层循环，遍历棋盘的每一行。 ROWS是棋盘的行数。
            for (int col = 0; col < COLS - 4; col++) {// 在每一行内，开始一个内层循环，遍历棋盘的每一列，但只遍历到倒数第五列。COLS是棋盘的列数，减去4是因为要检查连续5个棋子的情况。
                if (board[row][col] == player && board[row][col + 1] == player && board[row][col + 2] == player && board[row][col + 3] == player && board[row][col + 4] == player) {
                   // 检查当前位置(row, col)开始的连续5个棋子是否都属于同一个玩家。如果是，则玩家在水平方向上形成了一个连续的5个棋子，返回true表示玩家获胜
					return true;
                }
            }
        }
        //// 检查纵向
        for (int col = 0; col < COLS; col++) {
            for (int row = 0; row < ROWS - 4; row++) {
                if (board[row][col] == player && board[row + 1][col] == player && board[row + 2][col] == player && board[row + 3][col] == player && board[row + 4][col] == player) {
                    return true;
                }
            }
        }
        //// 检查正斜线
        for (int row = 0; row < ROWS - 4; row++) {
            for (int col = 0; col < COLS - 4; col++) {
                if (board[row][col] == player && board[row + 1][col + 1] == player && board[row + 2][col + 2] == player && board[row + 3][col + 3] == player && board[row + 4][col + 4] == player) {
                    return true;
                }
            }
        }
        ////检查反斜线
        for (int row = 4; row < ROWS; row++) {
            for (int col = 0; col < COLS - 4; col++) {
                if (board[row][col] == player && board[row - 1][col + 1] == player && board[row - 2][col + 2] == player && board[row - 3][col + 3] == player && board[row - 4][col + 4] == player) {
                    return true;
                }
            }
        }
        return false;// 如果没有找到任何连续5个棋子的情况，返回false，表示玩家没有获胜。
    }

    //// 重置游戏的方法，清空棋盘和落子记录，重置当前玩家为玩家1.一个resetGame方法的定义和一个main方法，用于启动一个名为GobangGame的游戏。
    private void resetGame() {// 定义一个私有方法resetGame，用于重置游戏的状态。
        for (int i = 0; i < ROWS; i++) {// 开始一个外层循环，遍历棋盘的每一行。
            for (int j = 0; j < COLS; j++) {// 在每一行内，开始一个内层循环，遍历棋盘的每一列。
                board[i][j] = 0;// 将棋盘上每个位置的值设置为0，表示没有棋子。
            }// 结束外内层循环。
        }// 结束外层循环。
        moveRecord.clear();// 清空记录棋子移动的列表或集合，为下一轮游戏做准备。
        currentPlayer = 1;// 将当前玩家设置为1，通常表示游戏的第一位玩家开始。
    }// 结束resetGame方法的定义。

    public static void main(String[] args) {// 定义一个公共静态的main方法，这是Java程序的入口点。
        SwingUtilities.invokeLater(() -> {// 使用SwingUtilities.invokeLater来确保GUI的更新在事件调度线程上执行。
            GobangGame game = new GobangGame();// 创建GobangGame类的一个实例，通常这个类是游戏的主窗口。
            game.setVisible(true);// 设置游戏窗口为可见，这将触发窗口的绘制和显示。
        });// 结束lambda表达式，并传递给invokeLater方法。
    }// 结束main方法的定义。
}
//resetGame方法用于重置游戏的状态，将棋盘上的所有位置清零，清除移动记录，并将当前玩家设置为1。
//main方法则是程序的入口点，它使用Swing框架来创建和显示游戏窗口。